/* Copyright 2009
 *
 * This program and the accompanying materials
 * are made available under the terms of the
 * Eclipse Public License v1.0 which accompanies
 * this distribution, and is available at
 *
 * 		http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
 * OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * Contributors:
 * 	   IBM Corporation - initial API and implementation for JDT/DLTK
 *     Sean W. Quinn - initial adoption for use with PHP from various sources.
 */
package org.eclipse.php.core;

import java.util.StringTokenizer;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.dltk.compiler.CharOperation;
import org.eclipse.dltk.core.IBuildpathEntry;
import org.eclipse.dltk.internal.core.BuildpathEntry;
import org.eclipse.php.internal.compiler.lookup.TypeNames;
import org.eclipse.php.internal.core.PHPCorePluginExt;
import org.eclipse.php.internal.core.PHPModelStatus;
import org.eclipse.php.internal.core.ast.util.UtilExt;
import org.eclipse.php.internal.core.util.Messages;

/**
 * Provides methods for checking Java-specific conventions such as name syntax.
 * <p>
 * This class provides static methods and constants only.
 * </p>
 *
 * @noinstantiate This class is not intended to be instantiated by clients.
 */
@SuppressWarnings("restriction")
public final class PHPConventions {

	private static final char SLASH = '/';
	private static final String PACKAGE_INFO = new String(TypeNames.PACKAGE_INFO_NAME);

	// ** From JavaConventions
	//private static final Scanner SCANNER = new Scanner(false /* comment */, true /* whitespace */, false /* nls */,
	//		ClassFileConstants.JDK1_3 /* sourceLevel */, null/* taskTag */, null/* taskPriorities */, true /* taskCaseSensitive */);

	private PHPConventions() {
	// Not instantiable
	}

	/*
	 * Returns the current identifier extracted by the scanner (without unicode
	 * escapes) from the given id and for the given source and compliance
	 * levels. Returns <code>null</code> if the id was not valid
	 * @see org.eclipse.jdt.core.JavaConventions
	 */
	private static synchronized char[] scannedIdentifier(String id, String sourceLevel, String complianceLevel) {
		if (id == null) {
			return null;
		}
		// TODO: Need to implement this!
		return null;
	}

	/**
	 * Validate the given compilation unit name for the given source and
	 * compliance levels.
	 * <p>
	 * A compilation unit name must obey the following rules:
	 * <ul>
	 * <li>it must not be null
	 * <li>it must be suffixed by a dot ('.') followed by one of the
	 * {@link PHPCorePluginExt#getJavaLikeExtensions() Java-like extensions}
	 * <li>its prefix must be a valid identifier
	 * <li>it must not contain any characters or substrings that are not valid
	 * on the file system on which workspace root is located.
	 * </ul>
	 * </p>
	 *
	 * @param name the name of a compilation unit
	 * @param sourceLevel the source level
	 * @param complianceLevel the compliance level
	 * @return a status object with code <code>IStatus.OK</code> if the given
	 *         name is valid as a compilation unit name, otherwise a status
	 *         object indicating what is wrong with the name
	 * @since 3.3
	 */
	public static IStatus validateProgramUnitName(String name, String sourceLevel, String complianceLevel) {
		if (name == null) {
			return new Status(IStatus.ERROR, PHPCorePluginExt.PLUGIN_ID, -1, Messages.convention_unit_nullName, null);
		}
		if (!UtilExt.isPhpLikeFileName(name)) {
			return new Status(IStatus.ERROR, PHPCorePluginExt.PLUGIN_ID, -1, Messages.convention_unit_notScriptName,
					null);
		}
		String identifier;
		int index;
		index = name.lastIndexOf('.');
		if (index == -1) {
			return new Status(IStatus.ERROR, PHPCorePluginExt.PLUGIN_ID, -1, Messages.convention_unit_notScriptName,
					null);
		}
		identifier = name.substring(0, index);
		// JSR-175 metadata strongly recommends "package-info.java" as the
		// file in which to store package annotations and
		// the package-level spec (replaces package.html)
		if (!identifier.equals(PACKAGE_INFO)) {
			IStatus status = validateIdentifier(identifier, sourceLevel, complianceLevel);
			if (!status.isOK()) {
				return status;
			}
		}
		IStatus status = ResourcesPlugin.getWorkspace().validateName(name, IResource.FILE);
		if (!status.isOK()) {
			return status;
		}
		return PHPModelStatus.VERIFIED_OK;
	}

	/**
	 * Validate the given field name for the given source and compliance levels.
	 * <p>
	 * Syntax of a field name corresponds to VariableDeclaratorId (JLS2 8.3).
	 * For example, <code>"x"</code>.
	 *
	 * @param name the name of a field
	 * @param sourceLevel the source level
	 * @param complianceLevel the compliance level
	 * @return a status object with code <code>IStatus.OK</code> if the given
	 *         name is valid as a field name, otherwise a status object
	 *         indicating what is wrong with the name
	 * @since 3.3
	 */
	public static IStatus validateFieldName(String name, String sourceLevel, String complianceLevel) {
		return validateIdentifier(name, sourceLevel, complianceLevel);
	}

	/**
	 * Validate the given Java identifier for the given source and compliance
	 * levels The identifier must not have the same spelling as a Java keyword,
	 * boolean literal (<code>"true"</code>, <code>"false"</code>), or null
	 * literal (<code>"null"</code>). See section 3.8 of the
	 * <em>Java Language Specification, Second Edition</em> (JLS2). A valid
	 * identifier can act as a simple type name, method name or field name.
	 *
	 * @param id the Java identifier
	 * @param sourceLevel the source level
	 * @param complianceLevel the compliance level
	 * @return a status object with code <code>IStatus.OK</code> if the given
	 *         identifier is a valid Java identifier, otherwise a status object
	 *         indicating what is wrong with the identifier
	 * @since 3.3
	 */
	public static IStatus validateIdentifier(String id, String sourceLevel, String complianceLevel) {
		if (scannedIdentifier(id, sourceLevel, complianceLevel) != null) {
			return PHPModelStatus.VERIFIED_OK;
		}
		else {
			return new Status(IStatus.ERROR, PHPCorePluginExt.PLUGIN_ID, -1, Messages.bind(
					Messages.convention_illegalIdentifier, id), null);
		}
	}

	/**
	 * Validate the given import declaration name for the given source and
	 * compliance levels.
	 * <p>
	 * The name of an import corresponds to a fully qualified type name or an
	 * on-demand package name as defined by ImportDeclaration (JLS2 7.5). For
	 * example, <code>"java.util.*"</code> or <code>"java.util.Hashtable"</code>.
	 *
	 * @param name the import declaration
	 * @param sourceLevel the source level
	 * @param complianceLevel the compliance level
	 * @return a status object with code <code>IStatus.OK</code> if the given
	 *         name is valid as an import declaration, otherwise a status object
	 *         indicating what is wrong with the name
	 * @since 3.3
	 */
	public static IStatus validateUseDeclaration(String name, String sourceLevel, String complianceLevel) {
		if (name == null || name.length() == 0) {
			return new Status(IStatus.ERROR, PHPCorePluginExt.PLUGIN_ID, -1, Messages.convention_use_nullUse, null);
		}
		if (name.charAt(name.length() - 1) == '*') {
			if (name.charAt(name.length() - 2) == '.') {
				return validateNamespaceName(name.substring(0, name.length() - 2), sourceLevel, complianceLevel);
			}
			else {
				return new Status(IStatus.ERROR, PHPCorePluginExt.PLUGIN_ID, -1,
						Messages.convention_use_unqualifiedUse, null);
			}
		}
		return validateNamespaceName(name, sourceLevel, complianceLevel);
	}

	/**
	 * Validate the given method name for the given source and compliance
	 * levels. The special names "&lt;init&gt;" and "&lt;clinit&gt;" are not
	 * valid.
	 * <p>
	 * The syntax for a method name is defined by Identifier of MethodDeclarator
	 * (JLS2 8.4). For example "println".
	 *
	 * @param name the name of a method
	 * @param sourceLevel the source level
	 * @param complianceLevel the compliance level
	 * @return a status object with code <code>IStatus.OK</code> if the given
	 *         name is valid as a method name, otherwise a status object
	 *         indicating what is wrong with the name
	 * @since 3.3
	 */
	public static IStatus validateMethodName(String name, String sourceLevel, String complianceLevel) {
		return validateIdentifier(name, sourceLevel, complianceLevel);
	}

	/**
	 * Validate the given package name for the given source and compliance
	 * levels.
	 * <p>
	 * The syntax of a package name corresponds to PackageName as defined by
	 * PackageDeclaration (JLS2 7.4). For example, <code>"java.lang"</code>.
	 * <p>
	 * Note that the given name must be a non-empty package name (that is,
	 * attempting to validate the default package will return an error status.)
	 * Also it must not contain any characters or substrings that are not valid
	 * on the file system on which workspace root is located.
	 *
	 * @param name the name of a package
	 * @param sourceLevel the source level
	 * @param complianceLevel the compliance level
	 * @return a status object with code <code>IStatus.OK</code> if the given
	 *         name is valid as a package name, otherwise a status object
	 *         indicating what is wrong with the name
	 * @since 3.3
	 */
	public static IStatus validateNamespaceName(String name, String sourceLevel, String complianceLevel) {

		if (name == null) {
			return new Status(IStatus.ERROR, PHPCorePluginExt.PLUGIN_ID, -1, Messages.convention_namespace_nullName,
					null);
		}
		int length = name.length();
		if (length == 0) {
			return new Status(IStatus.ERROR, PHPCorePluginExt.PLUGIN_ID, -1, Messages.convention_namespace_emptyName,
					null);
		}

		// **
		// For absolute path purposes, we should allow a leading slash as part
		// of the use syntax clauses... [SWQ]
		if (length == 1 && (name.charAt(0) == SLASH || name.charAt(length - 1) == SLASH)) {
			return new Status(IStatus.ERROR, PHPCorePluginExt.PLUGIN_ID, -1, Messages.convention_namespace_slashName,
					null);
		}
		if (CharOperation.isWhitespace(name.charAt(0)) || CharOperation.isWhitespace(name.charAt(name.length() - 1))) {
			return new Status(IStatus.ERROR, PHPCorePluginExt.PLUGIN_ID, -1,
					Messages.convention_namespace_nameWithBlanks, null);
		}

		int slash = 0;
		while (slash != -1 && slash < length - 1) {
			if ((slash = name.indexOf(SLASH, slash + 1)) != -1 && slash < length - 1 && name.charAt(slash + 1) == SLASH) {
				return new Status(IStatus.ERROR, PHPCorePluginExt.PLUGIN_ID, -1,
						Messages.convention_namespace_consecutiveSlashesName, null);
			}
		}
		IWorkspace workspace = ResourcesPlugin.getWorkspace();
		StringTokenizer st = new StringTokenizer(name, "."); //$NON-NLS-1$
		boolean firstToken = true;
		IStatus warningStatus = null;
		while (st.hasMoreTokens()) {
			String typeName = st.nextToken();
			typeName = typeName.trim(); // grammar allows spaces
			char[] scannedID = scannedIdentifier(typeName, sourceLevel, complianceLevel);
			if (scannedID == null) {
				return new Status(IStatus.ERROR, PHPCorePluginExt.PLUGIN_ID, -1, Messages.bind(
						Messages.convention_illegalIdentifier, typeName), null);
			}
			IStatus status = workspace.validateName(new String(scannedID), IResource.FOLDER);
			if (!status.isOK()) {
				return status;
			}
			// ** From JavaConventions
			//if (firstToken && scannedID.length > 0 && ScannerHelper.isUpperCase(scannedID[0])) {
			//	if (warningStatus == null) {
			//		warningStatus = new Status(IStatus.WARNING, PHPCorePluginExt.PLUGIN_ID, -1,
			//				Messages.convention_namespace_uppercaseName, null);
			//	}
			//}
			firstToken = false;
		}
		if (warningStatus != null) {
			return warningStatus;
		}
		return PHPModelStatus.VERIFIED_OK;
	}

	/**
	 * Validate a given classpath and output location for a project, using the
	 * following rules:
	 * <ul>
	 * <li>Classpath entries cannot collide with each other; that is, all entry
	 * paths must be unique.
	 * <li>The project output location path cannot be null, must be absolute and
	 * located inside the project.
	 * <li>Specific output locations (specified on source entries) can be null,
	 * if not they must be located inside the project,
	 * <li>A project entry cannot refer to itself directly (that is, a project
	 * cannot prerequisite itself).
	 * <li>Classpath entries or output locations cannot coincidate or be nested
	 * in each other, except for the following scenarii listed below:
	 * <ul>
	 * <li>A source folder can coincidate with its own output location, in which
	 * case this output can then contain library archives. However, a specific
	 * output location cannot coincidate with any library or a distinct source
	 * folder than the one referring to it.</li>
	 * <li>A source/library folder can be nested in any source folder as long as
	 * the nested folder is excluded from the enclosing one.</li>
	 * <li>An output location can be nested in a source folder, if the source
	 * folder coincidates with the project itself, or if the output location is
	 * excluded from the source folder.
	 * </ul>
	 * </ul>
	 * Note that the classpath entries are not validated automatically. Only
	 * bound variables or containers are considered in the checking process
	 * (this allows to perform a consistency check on a classpath which has
	 * references to yet non existing projects, folders, ...).
	 * <p>
	 * This validation is intended to anticipate classpath issues prior to
	 * assigning it to a project. In particular, it will automatically be
	 * performed during the classpath setting operation (if validation fails,
	 * the classpath setting will not complete).
	 * <p>
	 *
	 * @param phpProject the given java project
	 * @param rawClasspath the given classpath
	 * @param projectOutputLocation the given output location
	 * @return a status object with code <code>IStatus.OK</code> if the given
	 *         classpath and output location are compatible, otherwise a status
	 *         object indicating what is wrong with the classpath or output
	 *         location
	 * @since 2.0
	 */
	public static IPHPModelStatus validateBuildpath(IPHPProject phpProject, IBuildpathEntry[] rawClasspath,
			IPath projectOutputLocation) {
		final IPHPModelStatus modelStatus = (IPHPModelStatus) BuildpathEntry
				.validateBuildpath(phpProject, rawClasspath);
		return modelStatus;
	}

	/**
	 * Returns a Java model status describing the problem related to this
	 * classpath entry if any, a status object with code <code>IStatus.OK</code>
	 * if the entry is fine (that is, if the given classpath entry denotes a
	 * valid element to be referenced onto a classpath).
	 *
	 * @param project the given java project
	 * @param entry the given classpath entry
	 * @param checkSourceAttachment a flag to determine if source attachment
	 *            should be checked
	 * @return a java model status describing the problem related to this
	 *         classpath entry if any, a status object with code
	 *         <code>IStatus.OK</code> if the entry is fine
	 * @since 2.0
	 */
	public static IPHPModelStatus validateBuildpathEntry(IPHPProject project, IBuildpathEntry entry,
			boolean checkSourceAttachment) {
		final IPHPModelStatus modelStatus = (IPHPModelStatus) BuildpathEntry.validateBuildpathEntry(project, entry,
				false);
		return modelStatus;
	}

	/**
	 * Validate the given type variable name for the given source and compliance
	 * levels.
	 * <p>
	 * Syntax of a type variable name corresponds to a Java identifier (JLS3
	 * 4.3). For example, <code>"E"</code>.
	 *
	 * @param name the name of a type variable
	 * @param sourceLevel the source level
	 * @param complianceLevel the compliance level
	 * @return a status object with code <code>IStatus.OK</code> if the given
	 *         name is valid as a type variable name, otherwise a status object
	 *         indicating what is wrong with the name
	 * @since 3.3
	 */
	public static IStatus validateTypeVariableName(String name, String sourceLevel, String complianceLevel) {
		return validateIdentifier(name, sourceLevel, complianceLevel);
	}
}
